package com.lblin.method.util;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 
 * @author vi
 *
 */
public class ClassPathCandidateComponentScanner {

    public final String CLASS_SUFFIX = ".class";

    private final Pattern INNER_PATTERN = java.util.regex.Pattern.compile("\\$(\\d+).", java.util.regex.Pattern.CASE_INSENSITIVE);
    
    private boolean isPattern(String path) {
    	return path.indexOf('*')!=-1||path.indexOf('?')!=-1;
    }
    
    private final String ClASS_PREFIX_All = "classpath*:";
    
    private final String ClASS_PREFIX_LOCAL = "classpath:";
    
    private boolean searchAll = false;
    
    private Map<String, String> classMap = new HashMap<>(32);

    public Set<Class<?>> findCandidateComponents(String packageName) throws IOException {
    	
    	if(packageName.startsWith("classpath*:")) {
    		packageName = packageName.substring(ClASS_PREFIX_All.length());
    		searchAll = true;
    	}else if(packageName.startsWith("classpath:")) {
    		packageName = packageName.substring(ClASS_PREFIX_LOCAL.length());
    	}
    	
        if (packageName.endsWith(".")) {
            packageName = packageName.substring(0, packageName.length()-1);
        }
        
        String path = packageName.replace(".", "/");
        Enumeration<URL> urls = null;
        
        if(isPattern(path)) {
        	findMatchClassPathResources(path);
        }else {
        	urls = findAllClassPathResources(path);
        	//将资源路径中的class加载到map
            addClass(urls,packageName);
        }
        

        Set<Class<?>> set = new LinkedHashSet<>(classMap.size());
        for(String key : classMap.keySet()){
            String className = classMap.get(key);
            try {
            	Class<?> clazz = Class.forName(className);
            	set.add(clazz);
            }catch (Throwable e) {
				// TODO: handle exception
            	e.printStackTrace();
			}
        }
        return set;
    }


	private void addClass(Enumeration<URL> urls,String packageName) throws IOException {
		// TODO Auto-generated method stub
		while (urls!=null && urls.hasMoreElements()) {
            URL url = urls.nextElement();
            String protocol = url.getProtocol();
            if ("file".equals(protocol)&&!searchAll) {
                String file = URLDecoder.decode(url.getFile(), "UTF-8");
                File dir = new File(file);
                if(dir.isDirectory()){
                    parseClassFile(dir, packageName, classMap);
                }else {
                    throw new IllegalArgumentException("file must be directory");
                }
            } else if ("jar".equals(protocol)&&searchAll) {
                parseJarFile(url, packageName, classMap);
            }
        }
	}


	protected void parseClassFile(File dir, String packageName, Map<String, String> classMap){
        if(dir.isDirectory()){
            File[] files = dir.listFiles();
            for (File file : files) {
                parseClassFile(file, packageName, classMap);
            }
        } else if(dir.getName().endsWith(CLASS_SUFFIX)) {
            String name = dir.getPath();
            name = name.substring(name.indexOf("classes")+8).replace("\\", ".");
            //System.out.println("file:"+dir+"\t class:"+name);
            addToClassMap(name, classMap);
        }
    }

    protected void parseJarFile(URL url, String packageName, Map<String, String> classMap) throws IOException {
    	JarURLConnection jarConn = ((JarURLConnection) url.openConnection());
        JarFile jar = jarConn.getJarFile();
        String matchPath = jarConn.getEntryName();
        Enumeration<JarEntry> entries = jar.entries();
        while (entries.hasMoreElements()) {
            JarEntry entry = entries.nextElement();
            if (entry.isDirectory()) {
                continue;
            }
            String name = entry.getName();
            if(name.endsWith(CLASS_SUFFIX)&&pathMatch(name,packageName)){
            	addToClassMap(name.replace("/", "."), classMap);
            }
        }
    }
    
    private boolean pathMatch(String path,String patternPath) {
    	String regex = patternPath.replaceAll("\\*|\\?", ".*?");
    	Pattern p = Pattern.compile(regex+"/");
    	Matcher m = p.matcher(path);
    	return m.find();
    }
    

    private boolean addToClassMap(String name, Map<String, String> classMap){

        if(INNER_PATTERN.matcher(name).find()){ //过滤掉匿名内部类
            //System.out.println("anonymous inner class:"+name);
            return false;
        }
        //System.out.println("class:"+name);
        if(name.indexOf("$")>0){ //过滤掉内部类
        	return false;
        }
        if(!classMap.containsKey(name)){
            classMap.put(name, name.substring(0, name.length()-6)); //去掉.class
        }
        return true;
    }

    protected Enumeration<URL> findAllClassPathResources(String path) throws IOException {
        if(path.startsWith("/")){
            path = path.substring(1);
        }
        Enumeration<URL> urls = this.getClass().getClassLoader().getResources(path);

        return urls;
    }
    
    private void findMatchClassPathResources(String path) throws IOException {

    	findMatchLocal(path);//只加载classpath下的class
    	findMatchJar(path);//只加载jar中的class
	}
    
    private void findMatchJar(String path) throws IOException {
    	String rootDir = path.substring(0,path.indexOf("*")-1);
    	Enumeration<URL> urls = this.getClass().getClassLoader().getResources(rootDir);
    	addClass(urls,path);
	}


	private void findMatchLocal(String path) throws IOException {
		String rootDir = path.substring(0,path.indexOf("*")-1);
    	
    	String suffixDir = path.substring(path.indexOf("*")+1);
    	
    	URL url = this.getClass().getResource("/"+rootDir);
    	
    	File file = new File(url.getPath());
		if(file.exists()&&file.isDirectory()) {//classpath:下没有
			File[] files = file.listFiles();
			for(File f:files) {
				if(f.isDirectory()) {
					String newFindPath = rootDir+"/"+f.getName()+suffixDir;
					if(isPattern(newFindPath)) {
						//继续往下查
						findMatchLocal(newFindPath);
					}else {
						Enumeration<URL> resulturls = findAllClassPathResources(newFindPath);
						boolean temp = searchAll;
						searchAll = false;
						//将资源路径中的class加载到map
			            addClass(resulturls,newFindPath);//此时只加载classpath
			            searchAll = temp;
					}
				}
			}
		}
	}

    
    public static void main(String[] args) throws IOException {
//          Set<Class<?>> result = new ClassPathCandidateComponentScanner().findCandidateComponents("classpath*:com.*.*.util");
//          for(Class<?> c : result)
//        	  System.out.println(c.toString());
//          
//          
//          System.out.println(result.size());
    	
    	Object a = new ClassPathCandidateComponentScanner();
    	
    	System.out.println(a instanceof String);
	}

}
